package sketch.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;

import randoop.ExecutableSequence;
import randoop.Globals;
import randoop.MSequence;
import randoop.PrimitiveOrStringOrNullDecl;
import randoop.RConstructor;
import randoop.RMethod;
import randoop.Sequence;
import randoop.StatementKind;
import randoop.Variable;
import randoop.main.GenInputsAbstract;
import randoop.util.PrimitiveTypes;
import randoop.util.Reflection;
import randoop.util.Util;
import sketch.ounit.fuzz.StatementDumperWithOffset;

public class RandoopUtil {
	
	public static Class<?> getDeclaringClass(StatementKind statement) {
		if(statement instanceof RMethod) {
			RMethod rmethod = (RMethod)statement;
			return rmethod.getMethod().getDeclaringClass();
		} else if (statement instanceof RConstructor) {
			RConstructor rconstructor = (RConstructor)statement;
			return rconstructor.getConstructor().getDeclaringClass();
		} else {
			throw new RuntimeException("Wrong statement kind type: "
					+ statement + "  for getDeclaringClass method.");
		}
	}
	
	public static String getMethodName(StatementKind statement) {
		if(statement instanceof RConstructor) {
			return "<init>";
		} else if (statement instanceof RMethod) {
			return ((RMethod)(statement)).getMethod().getName();
		} else {
			throw new RuntimeException("Wrong statement kind type: "
					+ statement + "  for getMethodName method.");
		}
	}
	
	public static Class<?> getReturnType(StatementKind statement) {
		Checker.checkTrue(statement instanceof RMethod, "The StatementKind object: "
				+ statement + "need to be RMethod type.");
		RMethod rmethod = (RMethod)statement;
		return rmethod.getOutputType();
	}
	
	public static boolean hasParam(StatementKind statement) {
		return getParamNum(statement) != 0;
	}
	
	public static int getParamNum(StatementKind statement) {
		Checker.checkTrue(statement instanceof RMethod || statement instanceof RConstructor,
				"The StatementKind object: " + statement + " needs to be either RMethod or RConstructor!");
		if(statement instanceof RMethod) {
			RMethod rmethod = (RMethod)statement;
			return rmethod.getMethod().getParameterTypes().length;
		} else if (statement instanceof RConstructor) {
			RConstructor rconstructor = (RConstructor)statement;
			return rconstructor.getConstructor().getParameterTypes().length;
		} else {
			throw new RuntimeException("In correct statement type: " + statement);
		}
	}
	
	public static boolean isStaticMethod(StatementKind statement) {
		if(statement instanceof RMethod) {
			RMethod rmethod = (RMethod)statement;
			return rmethod.isStatic();
		} else {
			return false;
		}
	}
	
	/**
	 * Append the index-th statement to the stringbuilder sb, and adding offset
	 * The offset here only means changing variable names.
	 * */
	public static void appendSequenceStatementWithOffset(int offset, Sequence sequence, StringBuilder sb, int index) {
		Checker.checkTrue(offset >= 0, "The offset must >= 0, but now it is: " + offset);
		Checker.checkNull(sequence, "The sequence could not be null.");
		Checker.checkNull(sb, "The sb could not be null.");
		Checker.checkTrue(index >= 0 && index < sequence.size(), "The index: " + index
				+ " is not valid, the sequence length: " + sequence.size());
		//append the offset
		//sequence.printStatement(sb, index);
//		Variable newVariable = sequence.getVariable(index);
//		List<Variable> usedVars = sequence.getInputs(index);
//		//do the renaming here, adding the offset
//		Variable newVariableAfterRenaming = new Variable(newVariable.sequence, newVariable.index + offset);
//		List<Variable> usedVarAfterRenaming = new LinkedList<Variable>();
//		for(Variable used_var : usedVars) {
//			usedVarAfterRenaming.add(new Variable(used_var.sequence, used_var.index + offset));
//		}
//	    sequence.getStatementKind(index).appendCode(newVariableAfterRenaming, usedVarAfterRenaming, sb);
		StatementDumperWithOffset.dumpStatement(sequence.getStatementKind(index), sequence.getVariable(index), sequence.getInputs(index), offset, sb);
	}
	
	
	//taken from Randoop, slightly modified
	public static void appendConstructor(RConstructor rconstructor, String varName, String[] inputVars, StringBuilder sb) {
		Checker.checkNull(rconstructor, "The constructor could not be null.");
		Checker.checkTrue(rconstructor.getInputTypes().size() == inputVars.length,
				"The required type size: " + rconstructor.getInputTypes().size() + " != input vars: " + inputVars.length);

		Constructor<?> constructor = rconstructor.getConstructor();
	    Class<?> declaringClass = constructor.getDeclaringClass();
	    boolean isNonStaticMember = !Modifier.isStatic(declaringClass.getModifiers()) && declaringClass.isMemberClass();
	    assert Util.implies(isNonStaticMember, inputVars.length> 0);

	    // Note on isNonStaticMember: if a class is a non-static member class, the
	    // runtime signature of the constructor will have an additional argument
	    // (as the first argument) corresponding to the owning object. When printing
	    // it out as source code, we need to treat it as a special case: instead
	    // of printing "new Foo(x,y.z)" we have to print "x.new Foo(y,z)".

	    // TODO the last replace is ugly. There should be a method that does it.
	    String declaringClassStr = Reflection.getCompilableName(declaringClass);

	    sb.append(declaringClassStr + " " + varName + " = "
	        + (isNonStaticMember ? inputVars[0] + "." : "")
	        + "new "
	        + (isNonStaticMember ? declaringClass.getSimpleName() : declaringClassStr)
	        + "(");
	    for (int i = (isNonStaticMember ? 1 : 0) ; i < inputVars.length ; i++) {
	      if (i > (isNonStaticMember ? 1 : 0))
	        sb.append(", ");

	      sb.append(inputVars[i]);
	    }
	    sb.append(");");
	    sb.append(Globals.lineSep);
	}
	
	/**
	 * print a sequence with renaming variables
	 * **/
	public static void appendSequenceWithOffset(Sequence sequence, int offset, StringBuilder sb) {
//		System.out.println("original sequence: ");
//		System.out.println(sequence.toCodeString());
		Checker.checkTrue(offset >= 0, "The offset: " + offset + " is not legal.");
	    for (int i = 0; i < sequence.size(); i++) {
	      //append the i-th statement
//	      sequence.getStatementKind(i).appendCode(sequence.getVariable(i), sequence.getInputs(i), sb);
	      StatementDumperWithOffset.dumpStatement(sequence.getStatementKind(i), sequence.getVariable(i), sequence.getInputs(i), offset, sb);
	    }
	}
}